using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Media.Animation;
using System.Windows.Threading;

using MouseEventArgs = System.Windows.Input.MouseEventArgs;

namespace TraceView.UI.CustomControls
{
    public class TaskbarNotifier : Window, INotifyPropertyChanged
    {
        private DoubleAnimation animation;

        private EventHandler arrivedHidden;
        private EventHandler arrivedOpened;
        private DisplayStates displayState;
        private double hiddenTop;
        private int hidingMilliseconds = 1000;
        private int leftOffset;
        private double openedTop;
        private int openingMilliseconds = 1000;
        private int stayOpenMilliseconds = 1000;
        private DispatcherTimer stayOpenTimer;
        private Storyboard storyboard;

        public TaskbarNotifier()
        {
            Loaded += TaskbarNotifierLoaded;
        }

        public int OpeningMilliseconds
        {
            get { return openingMilliseconds; }
            set
            {
                openingMilliseconds = value;
                OnPropertyChanged("OpeningMilliseconds");
            }
        }

        public int HidingMilliseconds
        {
            get { return hidingMilliseconds; }
            set
            {
                hidingMilliseconds = value;
                OnPropertyChanged("HidingMilliseconds");
            }
        }

        public int StayOpenMilliseconds
        {
            get { return stayOpenMilliseconds; }
            set
            {
                stayOpenMilliseconds = value;
                if (stayOpenTimer != null)
                    stayOpenTimer.Interval = TimeSpan.FromMilliseconds(stayOpenMilliseconds);
                OnPropertyChanged("StayOpenMilliseconds");
            }
        }

        public int LeftOffset
        {
            get { return leftOffset; }
            set
            {
                leftOffset = value;
                OnPropertyChanged("LeftOffset");
            }
        }

        private DisplayStates DisplayState
        {
            get { return displayState; }
            set
            {
                if (value != displayState)
                {
                    displayState = value;
                    OnDisplayStateChanged();
                }
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        private void TaskbarNotifierLoaded(object sender, RoutedEventArgs e)
        {
            SetInitialLocations(false);

            DisplayState = DisplayStates.Hidden;

            stayOpenTimer = new DispatcherTimer
                                {
                                    Interval = TimeSpan.FromMilliseconds(stayOpenMilliseconds)
                                };
            stayOpenTimer.Tick += stayOpenTimer_Elapsed;
            animation = new DoubleAnimation();
            Storyboard.SetTargetProperty(animation, new PropertyPath(TopProperty));
            storyboard = new Storyboard();
            storyboard.Children.Add(animation);
            storyboard.FillBehavior = FillBehavior.Stop;
            arrivedHidden = new EventHandler(Storyboard_ArrivedHidden);
            arrivedOpened = new EventHandler(Storyboard_ArrivedOpened);
        }

        protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            base.OnPropertyChanged(e);
            if (e.Property.Name == "Top")
            {
                if (((double) e.NewValue != (double) e.OldValue) && ((double) e.OldValue != hiddenTop))
                {
                    BringToTop();
                }
            }
        }

        protected override void OnInitialized(EventArgs e)
        {
            WindowStyle = WindowStyle.None;
            ResizeMode = ResizeMode.NoResize;
            ShowInTaskbar = false;
            base.OnInitialized(e);
        }

        private void SetInitialLocations(bool showOpened)
        {
            var workingArea = new Rectangle((int) Left, (int) Top, (int) ActualWidth, (int) ActualHeight);
            workingArea = Screen.GetWorkingArea(workingArea);
            Left = workingArea.Right - ActualWidth - leftOffset;
            hiddenTop = workingArea.Bottom;
            openedTop = workingArea.Bottom - ActualHeight;
            Top = showOpened ? openedTop : hiddenTop;
        }

        private void BringToTop()
        {
            Topmost = true;
            Topmost = false;
        }

        private void OnDisplayStateChanged()
        {
            if (storyboard == null)
                return;
            storyboard.Stop(this);
            storyboard.Completed -= arrivedHidden;
            storyboard.Completed -= arrivedOpened;

            if (displayState != DisplayStates.Hidden)
            {
                BringToTop();
            }

            if (displayState == DisplayStates.Opened)
            {
                SetInitialLocations(true);
                if (!IsMouseOver)
                {
                    stayOpenTimer.Stop();
                    stayOpenTimer.Start();
                }
            }
            else if (displayState == DisplayStates.Opening)
            {
                Visibility = Visibility.Visible;
                BringToTop();
                int milliseconds = CalculateMillseconds(openingMilliseconds, openedTop);

                if (milliseconds < 1)
                {
                    DisplayState = DisplayStates.Opened;
                    return;
                }
                animation.To = openedTop;
                animation.Duration = new Duration(new TimeSpan(0, 0, 0, 0, milliseconds));
                storyboard.Completed += arrivedOpened;
                storyboard.Begin(this, true);
            }
            else if (displayState == DisplayStates.Hiding)
            {
                int milliseconds = CalculateMillseconds(hidingMilliseconds, hiddenTop);

                if (milliseconds < 1)
                {
                    DisplayState = DisplayStates.Hidden;
                    return;
                }
                animation.To = hiddenTop;
                animation.Duration = new Duration(new TimeSpan(0, 0, 0, 0, milliseconds));

                // Set the specific completed event handler.
                storyboard.Completed += arrivedHidden;

                // Start the animation.
                storyboard.Begin(this, true);
            }
            else if (displayState == DisplayStates.Hidden)
            {
                // Ensure the window is in the hidden position.
                SetInitialLocations(false);

                // Hide the window.
                Visibility = Visibility.Hidden;
            }
        }

        private int CalculateMillseconds(int totalMillsecondsNormally, double destination)
        {
            if (Top == destination)
            {
                // The window is already at its destination.  Nothing to do.
                return 0;
            }

            double distanceRemaining = Math.Abs(Top - destination);
            double percentDone = distanceRemaining/ActualHeight;

            // Determine the percentage of normal milliseconds that are actually required.
            return (int) (totalMillsecondsNormally*percentDone);
        }

        protected virtual void Storyboard_ArrivedHidden(object sender, EventArgs e)
        {
            // Setting the display state will result in any needed actions.
            DisplayState = DisplayStates.Hidden;
        }

        protected virtual void Storyboard_ArrivedOpened(object sender, EventArgs e)
        {
            // Setting the display state will result in any needed actions.
            DisplayState = DisplayStates.Opened;
        }

        private void stayOpenTimer_Elapsed(Object sender, EventArgs args)
        {
            // Stop the timer because this should not be an ongoing event.
            stayOpenTimer.Stop();

            if (!IsMouseOver)
            {
                // Only start closing the window if the mouse is not over it.
                DisplayState = DisplayStates.Hiding;
            }
        }

        protected override void OnMouseEnter(MouseEventArgs e)
        {
            if (DisplayState == DisplayStates.Opened)
            {
                // When the user mouses over and the window is already open, it should stay open.
                // Stop the timer that would have otherwise hidden it.
                stayOpenTimer.Stop();
            }
            else if ((DisplayState == DisplayStates.Hidden) ||
                     (DisplayState == DisplayStates.Hiding))
            {
                // When the user mouses over and the window is hidden or hiding, it should open. 
                DisplayState = DisplayStates.Opening;
            }

            base.OnMouseEnter(e);
        }

        protected override void OnMouseLeave(MouseEventArgs e)
        {
            if (DisplayState == DisplayStates.Opened)
            {
                // When the user moves the mouse out of the window, the timer to hide the window
                // should be started.
                stayOpenTimer.Stop();
                stayOpenTimer.Start();
            }

            base.OnMouseEnter(e);
        }

        public void Notify()
        {
            if (DisplayState == DisplayStates.Opened)
            {
                // The window is already open, and should now remain open for another count.
                stayOpenTimer.Stop();
                stayOpenTimer.Start();
            }
            else
            {
                DisplayState = DisplayStates.Opening;
            }
        }

        /// <summary>
        /// Force the window to immediately move to the hidden state.
        /// </summary>
        public void ForceHidden()
        {
            DisplayState = DisplayStates.Hidden;
        }

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #region Nested type: DisplayStates

        /// <summary>
        /// Internal states.
        /// </summary>
        private enum DisplayStates
        {
            Opening,
            Opened,
            Hiding,
            Hidden
        }

        #endregion
    }
}